<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Frame;

use Srv\Libs\Boot;
use Srv\Libs\Common\CommTime;
use Srv\Libs\ConfModel\LogsConf;

final class Logs
{
    private const CACHE_SIZE_MAX                        = 256;          // 缓存大小最大数
    private const CACHE_TIME_MAX                        = 10;           // 缓存最大时长[秒]
    private const NONE                                  = 0;            // NONE-0
    private const FATAL                                 = 1;            // FATAL-1
    private const ERROR                                 = 2;            // 错误-2
    private const EXCEPTION                             = 3;            // 异常-3
    private const WARN                                  = 4;            // 警告-4
    private const INFO                                  = 5;            // 信息-5
    private const DEBUG                                 = 6;            // 调式-6
    private const TRACE                                 = 7;            // 跟踪-7
    private static int $level                           = 0;            // 日志等级[0-none,1-fatal,2-error,3-exception,4=warn,5-info,6-debug,7-trace]
    private static int $cacheSize                       = 0;            // 日志缓存大小[0-立即写入日志]
    private static int $cacheTime                       = 0;            // 日志缓存时长[秒][0-每个间歇写入日志]
    private static string $fatalFile                    = '';           // FatalPath
    private static string $errorFile                    = '';           // ErrorPath
    private static string $exceptionFile                = '';           // ExceptionPath
    private static string $warnFile                     = '';           // WarnPath
    private static string $infoFile                     = '';           // InfoPath
    private static string $debugFile                    = '';           // DebugPath
    private static string $traceFile                    = '';           // TracePath
    private static int $cacheLastSize                   = 0;            // 日志缓存最后数量
    private static int $cacheLastTime                   = 0;            // 日志缓存最后时间[秒]
    private static array $cacheContentList              = [];           // 日志缓存内容列表

    /**
     * @param LogsConf $LogsConf
     * @return bool
     * init
     */
    public static function init(LogsConf $LogsConf):bool
    {
        self::$level                = in_array($LogsConf->getLevel(), [self::FATAL, self::ERROR, self::EXCEPTION, self::WARN, self::INFO, self::DEBUG, self::TRACE], true) ? $LogsConf->getLevel() : self::NONE;
        self::$cacheSize            = min(max($LogsConf->getCacheSize(), 0), self::CACHE_SIZE_MAX);
        self::$cacheTime            = min(max($LogsConf->getCacheTime(), 0), self::CACHE_TIME_MAX);
        self::$fatalFile            = $LogsConf->getFatalFile();
        self::$errorFile            = $LogsConf->getErrorFile();
        self::$exceptionFile        = $LogsConf->getExceptionFile();
        self::$warnFile             = $LogsConf->getWarnFile();
        self::$infoFile             = $LogsConf->getInfoFile();
        self::$debugFile            = $LogsConf->getDebugFile();
        self::$traceFile            = $LogsConf->getTraceFile();
        self::$cacheLastSize        = 0;
        self::$cacheLastTime        = 0;
        self::$cacheContentList     = [];
        return true;
    }

    /**
     * @return void
     * flushAll
     */
    public static function flushAll():void
    {
        self::writeCacheFile();
    }

    /**
     * @return void
     * loopTimerFlush
     */
    public static function loopTimerFlush():void
    {
        if(self::$cacheSize <= 0) return;
        $currTime               = CommTime::getTimeStamp();
        if(self::$cacheTime > 0 && self::$cacheLastTime > 0 && ($currTime - self::$cacheLastTime) < self::$cacheTime) return;
        self::writeCacheFile();
    }

    /**
     * @param string $logString
     * @param int $fileLine
     * @param string $filePath
     * @param string $logFile
     * @return bool
     * fatal
     */
    public static function fatal(string $logString, int $fileLine = 0, string $filePath = '', string $logFile = ''):bool
    {
        return self::addLog(self::FATAL, $logString, $fileLine, $filePath, $logFile);
    }

    /**
     * @param string $logString
     * @param int $fileLine
     * @param string $filePath
     * @param string $logFile
     * @return bool
     * error
     */
    public static function error(string $logString, int $fileLine = 0, string $filePath = '', string $logFile = ''):bool
    {
        return self::addLog(self::ERROR, $logString, $fileLine, $filePath, $logFile);
    }

    /**
     * @param string $logString
     * @param int $fileLine
     * @param string $filePath
     * @param string $logFile
     * @return bool
     * exception
     */
    public static function exception(string $logString, int $fileLine = 0, string $filePath = '', string $logFile = ''):bool
    {
        return self::addLog(self::EXCEPTION, $logString, $fileLine, $filePath, $logFile);
    }

    /**
     * @param string $logString
     * @param int $fileLine
     * @param string $filePath
     * @param string $logFile
     * @return bool
     * warn
     */
    public static function warn(string $logString, int $fileLine = 0, string $filePath = '', string $logFile = ''):bool
    {
        return self::addLog(self::WARN, $logString, $fileLine, $filePath, $logFile);
    }

    /**
     * @param string $logString
     * @param int $fileLine
     * @param string $filePath
     * @param string $logFile
     * @return bool
     * info
     */
    public static function info(string $logString, int $fileLine = 0, string $filePath = '', string $logFile = ''):bool
    {
        return self::addLog(self::INFO, $logString, $fileLine, $filePath, $logFile);
    }

    /**
     * @param string $logString
     * @param int $fileLine
     * @param string $filePath
     * @param string $logFile
     * @return bool
     * debug
     */
    public static function debug(string $logString, int $fileLine = 0, string $filePath = '', string $logFile = ''):bool
    {
        return self::addLog(self::DEBUG, $logString, $fileLine, $filePath, $logFile);
    }

    /**
     * @param string $logString
     * @param int $fileLine
     * @param string $filePath
     * @param string $logFile
     * @return bool
     * trace
     */
    public static function trace(string $logString, int $fileLine = 0, string $filePath = '', string $logFile = ''):bool
    {
        return self::addLog(self::TRACE, $logString, $fileLine, $filePath, $logFile);
    }

    /**
     * @param string $path
     * @param string $relativePath
     * @return string
     * getRelativePath
     */
    private static function getRelativePath(string $path, string $relativePath = ''):string
    {
        $pathSize           = strlen($path);
        $relativePathSize   = strlen($relativePath);
        if($pathSize < 1 || $relativePathSize < 1 || $pathSize < $relativePathSize) return $path;
        return substr($path, 0, $relativePathSize) === $relativePath ? substr($path, $relativePathSize) : $path;
    }

    /**
     * @return string
     * getTimeString
     */
    private static function getTimeString():string
    {
        $currTimeMill   = CommTime::getTimeMilli(0);
        $currTime       = intval($currTimeMill / 1000);
        $timeMill       = $currTimeMill % 1000;
        return date('m-d H:i:s', $currTime).'.'.str_pad(strval(min($timeMill, 999)), 3, '0', STR_PAD_LEFT);
    }

    /**
     * @param int $level
     * @param string $logString
     * @param int $fileLine
     * @param string $filePath
     * @param string $logFile
     * @return bool
     * addLog
     */
    private static function addLog(int $level, string $logString, int $fileLine, string $filePath, string $logFile = ''):bool
    {
        if($level > self::$level || !in_array($level, [self::FATAL, self::ERROR, self::EXCEPTION, self::WARN, self::INFO, self::DEBUG, self::TRACE], true)) return true;
        if(strlen($logFile) < 1){
            switch ($level){
                case self::FATAL : {
                    $logFile    = self::$fatalFile;
                    break;
                }
                case self::ERROR : {
                    $logFile    = self::$errorFile;
                    break;
                }
                case self::EXCEPTION : {
                    $logFile    = self::$exceptionFile;
                    break;
                }
                case self::WARN : {
                    $logFile    = self::$warnFile;
                    break;
                }
                case self::INFO: {
                    $logFile    = self::$infoFile;
                    break;
                }
                case self::DEBUG : {
                    $logFile    = self::$debugFile;
                    break;
                }
                case self::TRACE : {
                    $logFile    = self::$traceFile;
                    break;
                }
            }
            if(strlen($logFile) < 1) return true;
        }
        $filePath       = self::getRelativePath($filePath, Boot::getRootPath());
        $pId            = posix_getpid();
        $logContent     = self::getTimeString().'|'.$level.'|'.$fileLine.'|'.$filePath.'|'.$pId.'|'.$logString."\n";
        if(self::$cacheSize > 0){
            ++self::$cacheLastSize;
            self::addCacheContent($logFile, $logContent);
            if(self::$cacheLastSize >= self::$cacheSize) self::writeCacheFile();
            return true;
        }else{
            return @file_put_contents($logFile, $logContent, FILE_APPEND | LOCK_EX) > 0;
        }
    }

    /**
     * @return void
     * writeCacheFile
     */
    private static function writeCacheFile():void
    {
        self::$cacheLastSize    = 0;
        self::$cacheLastTime    = CommTime::getTimeStamp();
        if(count(self::$cacheContentList) > 0) foreach (self::$cacheContentList as &$logCache){
            $logFile            = $logCache['file'];
            $logContent         = $logCache['text'];
            $logCache['text']   = '';
            if(strlen($logContent) > 0) @file_put_contents($logFile, $logContent, FILE_APPEND | LOCK_EX);
        }
    }

    /**
     * @param string $logFile
     * @param string $logContent
     * @return void
     * addCacheContent
     */
    private static function addCacheContent(string $logFile, string $logContent):void
    {
        if(strlen($logContent) < 1) return;
        $logFileKey     = md5($logFile);
        if(!isset(self::$cacheContentList[$logFileKey])){
            self::$cacheContentList[$logFileKey] = ['file' => $logFile, 'text' => $logContent];
        }else{
            self::$cacheContentList[$logFileKey]['text'] .= $logContent;
        }
    }
}